﻿// ------------------------------------------------------------------------------
// <copyright file="PageHeaderTextBlock.cs" company="skumaronline.com">
//    Copyright (C) 2012 Suresh Kumar T.
//
//    This program is free software; you can redistribute it and/or
//    modify it under the terms of the GNU General Public License
//    as published by the Free Software Foundation version 2
//    of the License.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    http://www.gnu.org/licenses/gpl-2.0.html
//
// </copyright>
// ------------------------------------------------------------------------------

namespace StoreAppLib.Controls
{
    using System;
    using System.Collections.Generic;
    using Windows.UI;
    using Windows.UI.Core;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media;

    /// <summary>
    /// Page header text block
    /// </summary>
    [TemplatePartAttribute(Name = "Presenter")]
    public class PageHeaderTextBlock : ContentControl
    {
        /// <summary>
        /// Text value dependency property
        /// </summary>
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(PageHeaderTextBlock), new PropertyMetadata(null));

        /// <summary>
        /// Arrow value dependency property
        /// </summary>
        public static readonly DependencyProperty ArrowProperty =
            DependencyProperty.Register("Arrow", typeof(string), typeof(PageHeaderTextBlock), new PropertyMetadata(null));

        /// <summary>
        /// LineStackingStrategy value dependency property
        /// </summary>
        public static readonly DependencyProperty LineStackingStrategyProperty =
            DependencyProperty.Register("LineStackingStrategy", typeof(LineStackingStrategy), typeof(PageHeaderTextBlock), new PropertyMetadata(LineStackingStrategy.BlockLineHeight));

        /// <summary>
        /// LineHeight value dependency property
        /// </summary>
        public static readonly DependencyProperty LineHeightProperty =
            DependencyProperty.Register("LineHeight", typeof(double), typeof(PageHeaderTextBlock), new PropertyMetadata(0));

        /// <summary>
        /// ArrowSize value dependency property
        /// </summary>
        public static readonly DependencyProperty ArrowSizeProperty =
            DependencyProperty.Register("ArrowSize", typeof(double), typeof(PageHeaderTextBlock), new PropertyMetadata(0));

        /// <summary>
        /// ArrowSize RenderTransform property dependency property
        /// </summary>
        public static readonly DependencyProperty ArrowRenderTransformProperty =
            DependencyProperty.Register("ArrowRenderTransform", typeof(Transform), typeof(PageHeaderTextBlock), new PropertyMetadata(null));
        
        /// <summary>
        /// Menu dependency property
        /// </summary>
        public static readonly DependencyProperty MenuProperty =
            DependencyProperty.Register("Menu", typeof(Menu), typeof(PageHeaderTextBlock), new PropertyMetadata(null));

        /// <summary>
        /// Global menu
        /// </summary>
        private static IList<IMenuItem> globalMenu = new List<IMenuItem>();

        /// <summary>
        /// Arrow text box
        /// </summary>
        private TextBlock arrow;

        /// <summary>
        /// Initializes static members of the <see cref="PageHeaderTextBlock" /> class
        /// </summary>
        static PageHeaderTextBlock()
        {
            PageHeaderTextBlock.MenuPointerOverBackground = Menu.MenuPointerOverBackground;
            PageHeaderTextBlock.MenuPressedBackground = Menu.MenuPressedBackground;
            PageHeaderTextBlock.MenuSeparatorColor = Menu.MenuSeparatorColor;
            PageHeaderTextBlock.MenuSeparatorThickness = Menu.MenuSeparatorThickness;
            PageHeaderTextBlock.MenuBackground = Menu.MenuBackground;
            PageHeaderTextBlock.MenuForeground = Menu.MenuForeground;
            PageHeaderTextBlock.MenuBorderColor = Menu.MenuBorderColor;
            PageHeaderTextBlock.MenuBorderThickness = Menu.MenuBorderThickness;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PageHeaderTextBlock" /> class
        /// </summary>
        public PageHeaderTextBlock()
        {
            this.DefaultStyleKey = typeof(PageHeaderTextBlock);

            if (this.Menu == null && PageHeaderTextBlock.globalMenu != null)
            {
                this.Menu = new Menu();
                this.Menu.PointerOverBackground = PageHeaderTextBlock.MenuPointerOverBackground;
                this.Menu.PressedBackground = PageHeaderTextBlock.MenuPressedBackground;
                this.Menu.SeparatorColor = PageHeaderTextBlock.MenuSeparatorColor;
                this.Menu.SeparatorThickness = PageHeaderTextBlock.MenuSeparatorThickness;
                this.Menu.Background = PageHeaderTextBlock.MenuBackground;
                this.Menu.Foreground = PageHeaderTextBlock.MenuForeground;
                this.Menu.BorderBrush = PageHeaderTextBlock.MenuBorderColor;
                this.Menu.BorderThickness = new Thickness(PageHeaderTextBlock.MenuBorderThickness);

                foreach (IMenuItem item in PageHeaderTextBlock.globalMenu)
                {
                    this.Menu.Items.Add(item);
                }

                this.Menu.MenuItemInvoked += this.OnMenuItemInvoked;
            }

            this.arrow = new TextBlock();
            this.arrow.LineStackingStrategy = this.LineStackingStrategy;
            this.arrow.FontFamily = new FontFamily("Segoe UI Symbol");
            this.arrow.TextTrimming = TextTrimming.None;
            this.arrow.TextWrapping = TextWrapping.NoWrap;

            this.Content = this.arrow;
        }

        /// <summary>
        /// Global menu invoked event. You can use the event to cancel global navigation or
        /// to send a value as navigation parameter to Navigating To page.
        /// </summary>
        public event GlobalNavigationInvokedEventHandler GlobalNavigationInvoked;

        /// <summary>
        /// Gets or sets default pinter over background color
        /// </summary>
        public static SolidColorBrush MenuPointerOverBackground { get; set; }

        /// <summary>
        /// Gets or sets default pressed background color
        /// </summary>
        public static SolidColorBrush MenuPressedBackground { get; set; }

        /// <summary>
        /// Gets or sets default menu separator color
        /// </summary>
        public static SolidColorBrush MenuSeparatorColor { get; set; }

        /// <summary>
        /// Gets or sets default menu separator thickness
        /// </summary>
        public static double MenuSeparatorThickness { get; set; }

        /// <summary>
        /// Gets or sets default menu background color
        /// </summary>
        public static SolidColorBrush MenuBackground { get; set; }

        /// <summary>
        /// Gets or sets default menu foreground color
        /// </summary>
        public static SolidColorBrush MenuForeground { get; set; }

        /// <summary>
        /// Gets or sets default menu border color
        /// </summary>
        public static SolidColorBrush MenuBorderColor { get; set; }

        /// <summary>
        /// Gets or sets default border thickness
        /// </summary>
        public static double MenuBorderThickness { get; set; }

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        /// <value>
        /// The text.
        /// </value>
        public string Text
        {
            get
            {
                return (string)this.GetValue(TextProperty);
            }

            set
            {
                this.SetValue(TextProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the arrow.
        /// </summary>
        /// <value>
        /// The arrow.
        /// </value>
        public string Arrow
        {
            get
            {
                return (string)this.GetValue(ArrowProperty);
            }

            set
            {
                this.SetValue(ArrowProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets transform information that affects the rendering position of a UIElement.
        /// </summary>
        /// <value>
        /// Describes the specifics of the desired render transform. The default value is null.
        /// </value>
        public Transform ArrowRenderTransform
        {
            get
            {
                return (Transform)this.GetValue(ArrowRenderTransformProperty);
            }

            set
            {
                this.SetValue(ArrowRenderTransformProperty, value);
            }
        }
        
        /// <summary>
        /// Gets or sets the arrow font size.
        /// </summary>
        /// <value>
        /// Font size
        /// </value>
        public double ArrowSize
        {
            get
            {
                return (double)this.GetValue(ArrowSizeProperty);
            }

            set
            {
                this.SetValue(ArrowSizeProperty, value);
            }
        }
        
        /// <summary>
        /// Gets or sets the height of each line of content.
        /// </summary>
        /// <value>
        /// The height of each line in pixels. A value of 0 indicates that the line height
        /// is determined automatically from the current font characteristics. The default
        /// is 0
        /// </value>
        public double LineHeight
        {
            get
            {
                return (double)this.GetValue(LineHeightProperty);
            }

            set
            {
                this.SetValue(LineHeightProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the menu
        /// </summary>
        public Menu Menu
        {
            get
            {
                return (Menu)this.GetValue(MenuProperty);
            }

            set
            {
                this.SetValue(MenuProperty, value);
            }
        }
        
        /// <summary>
        /// Gets or sets a value that indicates how a line box is determined for each
        /// line of text in the TextBlock.
        /// </summary>
        /// <value>
        /// A value that indicates how a line box is determined for each line of text
        /// in the TextBlock. The default is MaxHeight.
        /// </value>
        public LineStackingStrategy LineStackingStrategy
        {
            get
            {
                return (LineStackingStrategy)this.GetValue(LineStackingStrategyProperty);
            }

            set
            {
                this.SetValue(LineStackingStrategyProperty, value);
            }
        }

        /// <summary>
        /// Add menu items to global menu
        /// </summary>
        /// <param name="menuItem">Menu item</param>
        public static void AddMenu(IMenuItem menuItem)
        {
            PageHeaderTextBlock.globalMenu.Add(menuItem);
        }

        /// <summary>
        /// Invoked whenever application code or internal processes (such as a rebuilding
        /// layout pass) call ApplyTemplate. In simplest terms, this means the method
        /// is called just before a UI element displays in your app. Override this method
        /// to influence the default post-template logic of a class.
        /// </summary>
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.arrow.Text = this.Arrow;
            this.arrow.FontSize = this.ArrowSize;
            this.arrow.LineHeight = this.LineHeight;
        }

        /// <summary>
        /// When tap on the page header show global menu
        /// </summary>
        /// <param name="e">Tapped event argument</param>
        protected override void OnTapped(Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
            base.OnTapped(e);

            if (this.Menu.Items.Count > 0)
            {
                this.Menu.Show(this, PopupAlignment.BottomRight);
            }
        }

        /// <summary>
        /// Invoked when the value of the Content property changes.
        /// </summary>
        /// <param name="oldContent">The old value of the Content property</param>
        /// <param name="newContent">The new value of the Content property</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            if (newContent == null)
            {
                this.arrow.Text = this.Arrow;
                this.arrow.FontSize = this.ArrowSize;
                this.arrow.LineHeight = this.LineHeight;
                this.Content = this.arrow;
            }
            else
            {
                base.OnContentChanged(oldContent, newContent);
            }
        }

        /// <summary>
        /// Fires when global menu is selected, notify parent page and receive cancel request and navigation parameter
        /// </summary>
        /// <param name="sender">Menu trigger source</param>
        /// <param name="menuItem">Menu item</param>
        private async void OnMenuItemInvoked(object sender, MenuItem menuItem)
        {
            if (menuItem is GlobalNavigationMenuItem)
            {
                GlobalNavigationMenuItem item = menuItem as GlobalNavigationMenuItem;

                FrameworkElement element = this;

                bool cancel = false;

                //// If the menu is fired for this page, then cancel is true;
                while (element.Parent != null && !(element is Frame))
                {
                    element = element.Parent as FrameworkElement;

                    if (element is Page && element.GetType() == item.Page)
                    {
                        cancel = true;
                    }
                }

                GlobalNavigationEventArgs arguments = new GlobalNavigationEventArgs(element as Frame);

                arguments.Cancel = cancel;

                if (this.GlobalNavigationInvoked != null)
                {
                    //// Notify the parent page and receive cancel request or navigation parameter
                    this.GlobalNavigationInvoked(item, arguments);
                }

                if (!arguments.Cancel && arguments.Frame != null && arguments.Frame is Frame)
                {
                    await this.Dispatcher.RunAsync(
                        CoreDispatcherPriority.Normal,
                        () =>
                        {
                            arguments.Frame.Navigate(item.Page, arguments.NavigationParamenter);
                        });
                }
            }
        }
    }
}
